﻿#include <iostream>
#include <map>
#include <functional>
#include <cmath>
#include <fstream>
#include <sstream>
#include "common_typedef.h"

#ifdef _WIN32
#include <Windows.h>
#include "ftd2xx/ftd2xx.h"
#else
#include "linux-x86_64/ftd2xx.h"
#include <unistd.h>
void Sleep(int ms) { usleep(ms * 1000); }
#endif

using namespace std;
int EnumerateDevice(int& devCnt);
void MakeUpLadder(int period, uint8_t* dst, size_t len);
void MakeUpSquare(int period, uint8_t* dst, size_t len);
void MakeUpSine(int period, uint8_t* dst, size_t len);
void MakeUpBorder(int period, uint8_t* dst, size_t len);
void saveCvs(const string fileName, const uint8_t* data, size_t len);
void LoadFromCsv(const string fileName, uint8_t* data, size_t len);

int main(int argc, char* argv[])
{
    cout << "Hello PrjGieGieCppLoader" << endl;
    int devCnt = 0xffff;
    cout << "EnumerateDevice device... " << endl;
    int errCode = EnumerateDevice(devCnt);
    if (errCode != ERR_SUCCESS) {
        cout << "EnumerateDevice device failed with " << errCode << endl;
        return errCode;
    }
    cout << "EnumerateDevice device succeed at devCnt " << devCnt << endl;

    FT_HANDLE ftHandle;
    FT_STATUS sta = FT_Open(devCnt, &ftHandle);
    if (sta != FT_OK) {
        cout << "Open device failed with " << sta << endl;
        return sta;
    }
    FT_SetBitMode(ftHandle, 0xff, 0x40); // must config to sync 245 mode
    cout << "Start streaming..." << endl;

    uint8_t txBuffer[512] = { 0 }; // 245FIFO tranfers 512 Bytes Once and have to restart
    DWORD txWritten = 0;
    size_t period = sizeof(txBuffer);
    const string sigType(argc == 2 ? argv[1] : "square");
    cout << "sigType: " << sigType << endl;
    const map <string, std::function<void(int, uint8_t*, size_t)>> sigGenerate = {
        {"square", MakeUpSquare},
        {"ladder", MakeUpLadder},
        {"sine", MakeUpSine},
        {"border", MakeUpBorder},
    };
    auto itr = sigGenerate.find(sigType);
    if (itr == sigGenerate.end()) {
        LoadFromCsv(sigType, txBuffer, sizeof(txBuffer));
    } else {
        itr->second(period, txBuffer, sizeof(txBuffer));
        saveCvs(sigType + ".csv", txBuffer, sizeof(txBuffer));
    }
    while (1) {
        for (auto o : sigGenerate) {
            // FT_Write must work with FPGA signals
            o.second(period, txBuffer, sizeof(txBuffer));
            sta = FT_Write(ftHandle, txBuffer, sizeof(txBuffer), &txWritten);
            cout << ".";
            if (sta != FT_OK || txWritten != sizeof(txBuffer)) {
                cout << "FT_Write failed" << endl;
            }
            Sleep(100);
        }
    }



    FT_Close(ftHandle);
    cout << "Device " << devCnt << " Closed, written " << std::dec << sizeof(txBuffer) << " bytes" << endl;

    return ERR_SUCCESS;
}

int EnumerateDevice(int& devCnt)
{
    FT_STATUS ftStatus;
    FT_DEVICE_LIST_INFO_NODE* devInfo;
    DWORD numDevs;

    ftStatus = FT_CreateDeviceInfoList(&numDevs);
    cout << " > Found " << numDevs << " FT Device, EnumerateDevice with " << ftStatus << endl;

    if (ftStatus != FT_OK || numDevs == 0) {
        return ERR_DEVNEXIST;
    }
    devCnt = numDevs;

    // allocate storage for list based on numDevs 
    devInfo =
        (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE) * numDevs);
    if (devInfo == nullptr) {
        return ERR_FAILURE;
    }
    ftStatus = FT_GetDeviceInfoList(devInfo, &numDevs);
    if (ftStatus != FT_OK) {
        free(devInfo);
        return ERR_DEVNEXIST;
    }
    for (uint32_t i = 0; i < numDevs; i++) {
        string devDescription(devInfo[i].Description);
        cout << " > FT Device " << i << ": " << devDescription << endl;
        if (devDescription == "Dual Channel DAC" || devDescription == "Dual Channel DAC A") {
            devCnt = i;
        }
    }

    free(devInfo);
    return (DWORD)devCnt == numDevs ? ERR_DEVNEXIST : ERR_SUCCESS;
}

void MakeUpLadder(int period, uint8_t* dst, size_t len)
{
    size_t idx = 0;
    while (idx < len)
    {
        int subIdx = idx % period;
        dst[idx] = subIdx * 0xff / period;
        if (subIdx > period / 2) {
            dst[idx] = 0xff - dst[idx];
        }
        idx++;
    }
}

void MakeUpSquare(int period, uint8_t* dst, size_t len)
{
    size_t idx = 0;
    while (idx < len)
    {
        int subIdx = idx % period;
        dst[idx] = 0x0;
        if (subIdx >= period / 2) {
            dst[idx] = 0xff - dst[idx];
        }
        idx++;
    }
}

void MakeUpSine(int period, uint8_t* dst, size_t len)
{
    constexpr double PI = 3.141592653;
    size_t idx = 0;
    while (idx < len)
    {
        int subIdx = idx % period;
        dst[idx] = (sin((double)(subIdx) / (double)period * 2.0 * PI) + 1.0) * 0x7f;
        idx++;
    }
}

void saveCvs(const string fileName, const uint8_t* data, size_t len)
{
    fstream f;
    f.open(fileName, ios::out);
    for (size_t idx = 0; idx < len; idx++) {
        f << to_string(data[idx]) << ",\n";
    }
    f.close();
    cout << "saved Cvs to " << fileName << endl;
}

void MakeUpBorder(int period, uint8_t* dst, size_t len)
{
    (void)period;
    int borderLen = len / 8;

    for (int lineIdx = 0; lineIdx < 4; lineIdx++) {
        for (int ptIdx = 0; ptIdx < borderLen; ptIdx++) {
            switch (lineIdx) {
            case 0:
                dst[(lineIdx * borderLen + ptIdx )* 2 + 0] = 0xff;
                dst[(lineIdx * borderLen + ptIdx )* 2 + 1] = (uint32_t)0x000000ff * ptIdx / borderLen;
                break;
            case 1:
                dst[(lineIdx * borderLen + ptIdx )* 2 + 0] = (uint32_t)0x000000ff * (borderLen - ptIdx) / borderLen;
                dst[(lineIdx * borderLen + ptIdx )* 2 + 1] = 0xff;
                break;
            case 2:
                dst[(lineIdx * borderLen + ptIdx )* 2 + 0] = 0x0;
                dst[(lineIdx * borderLen + ptIdx )* 2 + 1] = (uint32_t)0x000000ff * (borderLen - ptIdx) / borderLen;
                break;
            case 3:
                dst[(lineIdx * borderLen + ptIdx )* 2 + 0] = (uint32_t)0x000000ff * ptIdx / borderLen;
                dst[(lineIdx * borderLen + ptIdx )* 2 + 1] = 0x0;
                break;
            default:
                dst[(lineIdx * borderLen + ptIdx )* 2 + 0] = 0x80;
                dst[(lineIdx * borderLen + ptIdx )* 2 + 1] = 0x80;
            }
        }
    }
}

void LoadFromCsv(const string fileName, uint8_t* data, size_t len)
{
    ifstream f;
    f.open(fileName, ios::in);
    if (!f.is_open()) {
        cout << "Load failed, Can't open " << fileName << endl;
        exit(-1);
    }
    string line;
    size_t idx = 0;
    for (; idx < len && getline(f, line); idx++) {
        data[idx] = static_cast<uint8_t>(std::stoi(line));
    }
    f.close();
    cout << "Load " << std::dec << idx / 2 << " pts from " << fileName << endl;
}